/**
 * @author Tobias Boehm
 *
 * 29.04.2011
 */
package org.anddev.andengine.extension.tbsengine.map;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Vector;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.content.Context;

/**
 * This is an abstract implementation of a {@link AbsMapOverview} loader. It 
 * gives you an overview of all {@link AbsMapOverview}s  of maps which reside in
 * a specific folder ({@link #loadMapOverviews()}), or you can request a 
 * specific map overview by providing a map ID ({@link #loadSpecificMapOverview(String)}).
 * <p>
 * Notice:<br>
 * To avoid repeated XML parsing of the same file all map overviews which where
 * requested during one session are cached inside an static repository. Do not 
 * hesitate to call {@link #loadMapOverviews()} and
 * {@link #loadSpecificMapOverview(String)} repeatedly.
 * 
 * @see {@link #sLoadedMapOverviewsCache}
 * 
 * 
 * @author Tobias Boehm
 *
 * @param <MapOverviewClass> - Your implementation of {@link AbsMapOverview}.
 */
public abstract class AbsMapOverviewLoader<MapOverviewClass extends AbsMapOverview> extends FilePathsResolver{
	
	/**
	 * This is the cache where all previously loaded map overviews are hold 
	 * available.
	 */
	protected static final HashMap<String,AbsMapOverview> sLoadedMapOverviewsCache = new HashMap<String, AbsMapOverview>();
	
	
	protected final Context mContext;
	
	
	/**
	 * This is the default constructor. Use this constructor as <code>super(...)
	 * </code> in your implementation of this class if you stick 
	 * {@link TBSmapConstants#STANDARD_MAP_FILE_SUFFIX} as your map file suffix.
	 * Additionally you have to provide the absolute path to your map's folder.
	 * 
	 * @param pContext
	 * @param pMapFolderPath - The absolute path to your map's folder.
	 * 
	 * @throws IOException if there is an problem by reading files 
	 */
	public AbsMapOverviewLoader(final Context pContext, final String pMapFolderPath) throws IOException {
		
		super(pContext, pMapFolderPath, STANDARD_MAP_FILE_SUFFIX);
		
		this.mContext = pContext;
	}

	/**
	 * This is an advanced constructor. Use this constructor as <code>super(...)
	 * </code> in your implementation of this class if you use a different 
	 * suffix for your map files.
	 * 
	 * @param pContext
	 * @param pMapFolderPath - The absolute path to your maps folder.
	 * @param pMapFilesSuffix - Your map file suffix. Example: <code>"tmx"</code>
	 * 
	 * @throws IOException if there is an problem by reading files 
	 */
	public AbsMapOverviewLoader(final Context pContext, final String pMapFolderPath,
								final String pMapFilesSuffix) throws IOException {
		
		super(pContext, pMapFolderPath, pMapFilesSuffix);
		
		this.mContext = pContext;
	}

	/**
	 * This method returns the map overviews related to the map files which
	 * reside inside the map folder (path) you provided by calling a constructor
	 * of this class.
	 *  
	 * @return a vector of map overviews.
	 * 
	 * @throws IOException if there is a problem with reading a file.
	 * @throws SAXException if there is a problem with the sax parser. 
	 * @throws ParserConfigurationException if there is a problem with the parser configuration.
	 */
	@SuppressWarnings("unchecked")
	public Vector<MapOverviewClass> loadMapOverviews() throws IOException, SAXException, ParserConfigurationException{
		
		final Vector<MapOverviewClass> mapOverviewsList = new Vector<MapOverviewClass>();
				
		final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
		final SAXParser saxParser = saxParserFactory.newSAXParser();
		
		for(String currentMapFilePath : this.mFilePaths){
			
			final MapOverviewClass mapOverview;
			
			if(sLoadedMapOverviewsCache.containsKey(currentMapFilePath)){
				
				mapOverview = (MapOverviewClass) sLoadedMapOverviewsCache.get(currentMapFilePath);
				
			}else{
								
				mapOverview = this.loadMapOverview(saxParser, currentMapFilePath);
				
				// Cache the MapOverview
				sLoadedMapOverviewsCache.put(mapOverview.getMapID(), mapOverview);
				sLoadedMapOverviewsCache.put(currentMapFilePath, mapOverview);
			}
			
			mapOverviewsList.add(mapOverview);
		}
		
		return mapOverviewsList;
	}
	
	private MapOverviewClass loadMapOverview(final SAXParser pSAXparser, final String pMapFilePath) throws SAXException, IOException{
		
		final XMLReader xmlReader = pSAXparser.getXMLReader();
		
		final BaseMapOverviewParser<MapOverviewClass> overviewParser = new BaseMapOverviewParser<MapOverviewClass>(this.createEmptyMapOverview(this.mFolderPath));

		// Parse Map XML
		xmlReader.setContentHandler(overviewParser);
		xmlReader.parse(new InputSource(new BufferedInputStream(this.mContext.getAssets().open(pMapFilePath))));			
		
		// Set Map Source file path
		final MapOverviewClass mapOverview = overviewParser.getTBSMapOverview();
		mapOverview.setMapFilePath(pMapFilePath);
		
		return mapOverview;
	}
	
	/**
	 * This method returns a specific map overview related to a map ID.
	 * 
	 * @param pMapID - The ID of the wanted map.
	 * 
	 * @return the map overview object.
	 * 
	 * @throws IOException if there is a problem with reading a file.
	 * @throws SAXException if there is a problem with the sax parser. 
	 */
	@SuppressWarnings("unchecked")
	public MapOverviewClass loadSpecificMapOverview(final String pMapID) throws SAXException, IOException{
		
		if(sLoadedMapOverviewsCache.containsKey(pMapID)){
			
			return (MapOverviewClass) sLoadedMapOverviewsCache.get(pMapID);
			
		}else{
			
			try {
				this.loadMapOverviews();
			} catch (ParserConfigurationException e) {
				
				throw new SAXException(e);
			}
			
			return (MapOverviewClass) sLoadedMapOverviewsCache.get(pMapID);
		}
	}
	
	/**
	 * This method should return an empty instance of your {@link AbsMapOverview}
	 * implementation.
	 * 
	 * @param pMapFilePath - The map file path you may need when you call the 
	 * 						 constructor of your {@link AbsMapOverview} implementation.
	 * 
	 * @return an empty instance of your {@link AbsMapOverview} implementation.
	 */
	protected abstract MapOverviewClass createEmptyMapOverview(final String pMapFilePath);
}
